package com.zzy.study.socketANDnio;

import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * 一个selector处理所有请求
 * wakeup()作用
 * selectNow()的作用
 * @author eason
 *
 */
public class NIOServerSingleThread implements Runnable{
	
	private static final Log LOG = LogFactory.getLog(NIOServerSingleThread.class);
	
	private ServerSocketChannel serverChannel;
	private SocketChannel socketChannel;
	private static Selector selector;
	private static final int BLOCK_SIZE = 1024;
	private ByteBuffer receiveBuffer = ByteBuffer.allocate(BLOCK_SIZE);
	private ByteBuffer sendBuffer = ByteBuffer.allocate(BLOCK_SIZE);
	private int count;
	private int flag = 0;
	private static final String SEND_STRING = "message from server ";
	
	public static void main(String[] args) {
		new Thread(new NIOServerSingleThread(9111)).start();       //线程1
		//演示wakeup()和selectNow()的作用
//		while(true) {
//			try {
//				Thread.sleep(1000 * 3L);
//				selector.wakeup();      //线程1正在执行int count = selector.select();阻塞的话，主线程里的selector.wakeup()立刻唤醒线程1
//				selector.selectNow();   //线程1未执行selector.select()的话，同时调用了selectNow，对于清除了wakeup唤醒的效果。
//			} catch (InterruptedException e) {
//				e.printStackTrace();
//			} catch (IOException e) {
//				e.printStackTrace();
//			}
//		}
	}

	public NIOServerSingleThread(int port) {
		init(port);
	}


	private void init(int port) {
		try {
			serverChannel = ServerSocketChannel.open();
			serverChannel.socket().bind(new InetSocketAddress(port));
			serverChannel.configureBlocking(false);
			selector = Selector.open();
			serverChannel.register(selector, SelectionKey.OP_ACCEPT);
			LOG.info(new String("server has start, listener port:" + port));
		} catch (IOException e) {
			LOG.error(e);
		}
	}
	
	public void run() {
		while(true) {
			try {
				LOG.info("before enter loop");
				int count = selector.select();
				LOG.info("after enter loop, count=" + count);
				if(count == 0) {continue;}
				Set<SelectionKey> selectionKeys = selector.selectedKeys();
				Iterator iterator = selectionKeys.iterator();
				while(iterator.hasNext()) {
					SelectionKey selectionKey = (SelectionKey) iterator.next();
					iterator.remove();
					
					if(selectionKey.isValid()){
						handleKey(selectionKey);
					}
						
				}
					
					
//				if(selector.select() > 0) {
//					Set<SelectionKey> selectionKeys = selector.selectedKeys();
//					Iterator iterator = selectionKeys.iterator();
//					while(iterator.hasNext()) {
//						SelectionKey selectionKey = (SelectionKey) iterator.next();
//						iterator.remove();
//						
//						if(selectionKey.isValid()){
//							handleKey(selectionKey);
//						}
//							
//					}
//				}
			} catch (IOException e) {
				LOG.error(e);
			} 
		}
	}


	private void handleKey(SelectionKey selectionKey) {
		try{
			if(selectionKey.isAcceptable()) {
				serverChannel = (ServerSocketChannel) selectionKey.channel();
				socketChannel = serverChannel.accept();
				LOG.info(socketChannel.socket().getRemoteSocketAddress() + " has a request!");
				socketChannel.configureBlocking(false);
				socketChannel.register(selector, SelectionKey.OP_READ);
			}else if(selectionKey.isReadable()) {
				socketChannel = (SocketChannel) selectionKey.channel();
				socketChannel.configureBlocking(false);
    			receiveBuffer.clear();
				count = socketChannel.read(receiveBuffer);
				if(count > 0) {
					LOG.info(new String(receiveBuffer.array(), 0, count) + " has received from client!");
					socketChannel.register(selector, SelectionKey.OP_WRITE);
				}
			}else if(selectionKey.isWritable()) {
				socketChannel = (SocketChannel) selectionKey.channel();  
    			sendBuffer.clear();  
    			String msg = SEND_STRING + flag++;
    			
    			ByteArrayOutputStream response = new ByteArrayOutputStream(10240);
    			DataOutputStream out = new DataOutputStream(response);
    			out.writeInt(msg.length());
    			out.write(msg.getBytes());
    			
    			sendBuffer.put(ByteBuffer.wrap(response.toByteArray()));
    			sendBuffer.flip();  
    			socketChannel.write(sendBuffer);  
    			LOG.info(new String(msg + " has set to client!"));
    			socketChannel.register(selector, SelectionKey.OP_READ);  
			}
		}catch (Exception e) {
			selectionKey.cancel();
			if (selectionKey.channel() != null){
				try {
					selectionKey.channel().close();
				} catch (IOException e1) {
					LOG.error(e);
				}
			}
		}
	}
	
}
